package icasue.tools.sqlhelper.core;

import icasue.tools.sqlhelper.core.AbstractSqlProviderFrame;
import icasue.tools.sqlhelper.core.KeyUsingRate;
import icasue.tools.sqlhelper.core.SQLSplit;
import lombok.SneakyThrows;
import icasue.tools.sqlhelper.common.Const;
import icasue.tools.sqlhelper.common.FieldsTouch;
import icasue.tools.sqlhelper.core.FrameParser;
import icasue.tools.sqlhelper.core.SQLSplit;
import icasue.tools.sqlhelper.core.KeyUsingRate;
import icasue.tools.sqlhelper.core.Consult;
import icasue.tools.sqlhelper.core.Sort;
import icasue.tools.sqlhelper.core.Limit;
import icasue.tools.sqlhelper.core.Condition;
import icasue.tools.sqlhelper.core.Queries;

import java.sql.SQLException;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static icasue.tools.sqlhelper.common.Const.*;

/**
 * @Author: Qiao Hang
 * @CreateDate: 2021/11/19 上午10:55
 * @UpdateDate:
 * @Description:
 */
class FieldRows extends FrameParser {

    @SneakyThrows(Throwable.class)
    final static <R> String _fieldRowsLimit(FieldsTouch<?,R> touch, boolean limitOne, AbstractSqlProviderFrame frame) {
        List<String> columns = _lambdaFieldsToColumns(frame, true, touch);
        if(columns.size() == 0){
            throw new SQLException(frame.database().concat($SPOT).concat(frame.table())
                    .concat(" supplied query field by lambda is invalid!"));
        }
        String resultMapSeq = String.join($DOT, columns);
        StringBuilder builder = new StringBuilder()
                // "select col "
                .append(SELECT).append($SPACE).append(resultMapSeq).append($SPACE)
                // "select col from "
                .append(FROM).append($SPACE)
                // "select col from `database`.`table` "
                .append(frame.database()).append($SPOT).append(frame.table()).append($SPACE);
        if(limitOne){
            // "select col from `database`.`table` limit 1 "
            builder.append(LIMIT).append($SPACE).append(1).append($SPACE);
        }
        String SQL = builder.toString();
        return SQL;
    }


    @SneakyThrows(Throwable.class)
    final static <R> String _fieldRowsSmartLimit(Object inst, FieldsTouch<?, R> touch, boolean limitOne, AbstractSqlProviderFrame frame) {
        // 动态条件序列.
        final StringBuilder conditionSeq = new StringBuilder();
        // 动态条件决策.
        final List<KeyUsingRate> keyUsingRates = _wisdomConditions(inst, frame);
        // 响应结果集映射。
        List<String> queryColumns = _lambdaFieldsToColumns(frame, true, touch);
        if(queryColumns.size() == 0){
            throw new SQLException(frame.database().concat($SPOT).concat(frame.table())
                    .concat(" supplied query field by lambda is invalid!"));
        }
        String resultMapSeq = String.join($DOT, queryColumns);

        // 获取查询条件的SQL段.
        final List<List<SQLSplit>> keysGroupSqlSplits = Stream.iterate((List<SQLSplit>) null, (left) -> new LinkedList<>())
                .skip(1)
                .limit(keyUsingRates.size())
                .collect(Collectors.toList());

        final AtomicInteger idx = new AtomicInteger(0);
        keyUsingRates.stream()
                .forEach(keyUsingRate -> {
                    List<SQLSplit> splits = keysGroupSqlSplits.get(idx.getAndIncrement());
                    for (int i = 0; i < keyUsingRate.values.length; i++) {
                        if(keyUsingRate.values[i] == null){
                            break;
                        }
                        SQLSplit sqlSplit = new SQLSplit(
                                SQLSplit.SplitOP.EQ,
                                keyUsingRate,
                                keyUsingRate.keys[i],
                                keyUsingRate.values[i]
                        );
                        splits.add(sqlSplit);
                    }
                });

        // 构建查询条件.
        for (List<SQLSplit> groupSqlSplit : keysGroupSqlSplits) {
            for (SQLSplit sqlSplit : groupSqlSplit) {
                conditionSeq
                        .append(sqlSplit.getSqlSegment(true))
                        .append($SPACE).append(AND).append($SPACE);
            }
        }

        if(conditionSeq.length() == 0){
            conditionSeq.append(Const.SQL_CONDITION_TRUE);
        }else {
            conditionSeq.replace(conditionSeq.length() - 5,conditionSeq.length(),$EMPTY);
        }


        StringBuilder builder = new StringBuilder()
                // "select [col] "
                .append(SELECT).append($SPACE).append(resultMapSeq).append($SPACE)
                // "select [col] from "
                .append(FROM).append($SPACE)
                // "select [col] from `database`.`table` "
                .append(frame.database()).append($SPOT).append(frame.table()).append($SPACE)
                // "select [col] from `database`.`table` where [`col1` = 'val1' and `col2` = 'val2'] "
                .append(WHERE).append($SPACE).append(conditionSeq.toString()).append($SPACE);
        if(limitOne){
            // "select [col] from `database`.`table` where [`col1` = 'val1' and `col2` = 'val2'] limit 1 "
            builder.append(LIMIT).append($SPACE).append(1).append($SPACE);
        }
        String SQL = builder.toString();
        return SQL;
    }

    @SneakyThrows(Throwable.class)
    final static <R> String _fieldRowsByConsultLimit(Consult consult, FieldsTouch<?, R> touch, boolean limitOne, AbstractSqlProviderFrame frame) {
        // JAVA属性到数据库列的映射.
        final Map<String, String> field_column = $fName_To_Column.apply(frame);

        // 查询条件序列.
        final StringBuilder conditionSeq = new StringBuilder();

        // 响应结果集映射。
        List<String> queryColumns = _lambdaFieldsToColumns(frame, true, touch);
        if(queryColumns.size() == 0){
            throw new SQLException(frame.database().concat($SPOT).concat(frame.table())
                    .concat(" supplied query field by lambda is invalid!"));
        }
        String resultMapSeq = String.join($DOT, queryColumns);

        // 查询排序条件.
        Optional<Sort> sortOptional = Optional.ofNullable(consult.sort);

        // 查询分页.
        Optional<Limit> limitOptional = Optional.ofNullable(consult.limit);

        /* 构建条件查询序列. */
        // 查询条件，对于复杂条件查询(包含 and，or，且存在查询组的) 仅做条件组织，不尝试进行优化
        // TODO: 这里将来是可以优化的地方，复杂度较高.

        // current query group's build buffer.
        final StringBuilder oneQueryGroup = new StringBuilder();
        // build all query groups.
        for (Queries query : (List<Queries>)consult.queries) {
            // create one query group, as format:
            // " and|or (col1 = 'val1',col2 like '%val2%')"
            // "(col1 = 'val1',col2 like '%val2%')"
            if(query.symbol != null){
                oneQueryGroup.append($SPACE).append(query.symbol).append($SPACE);
            }
            oneQueryGroup.append($BRACKETS_SMALL_LEFT);

            // build current query group's conditions.
            Boolean existInValidCondition = Boolean.FALSE;
            for (Condition condition : query.conditions) {
                if(condition.jField == null){
                    // condition build invoking onValid [.and() | .or()]
                    continue;
                }
                existInValidCondition = Boolean.TRUE;
                String column = field_column.get(condition.jField);
                boolean strictColumnMapping = true;
                if(column == null){
                    // JavaField not exist or is not a mapping field,
                    // perhaps user expect using "udf" as column to search, such as [length(geo_hash) > 6] !
                    // on this time, this query condition will retain.
                    column = condition.jField;
                    strictColumnMapping = false;
                }
                if(condition.logicRelationship != null){
                    oneQueryGroup.append($SPACE).append(condition.logicRelationship).append($SPACE);
                }
                SQLSplit sqlSplit = new SQLSplit(condition.operate, column, condition.values);
                oneQueryGroup.append(sqlSplit.getSqlSegment(strictColumnMapping));
            }
            oneQueryGroup.append($BRACKETS_SMALL_RIGHT).append($SPACE);

            // append this query group if exist inValid condition.
            if(existInValidCondition.booleanValue()){
                conditionSeq.append(oneQueryGroup.toString());
            }
            // clear current query group's build buffer.
            oneQueryGroup.replace(0, oneQueryGroup.length(), $EMPTY);
        }

        if(conditionSeq.length() == 0){
            conditionSeq.append(Const.SQL_CONDITION_TRUE);
        }else {
            conditionSeq.replace(conditionSeq.length() - 1, conditionSeq.length(), $EMPTY);
        }

        // base query sql.
        StringBuilder sqlBuild = new StringBuilder()
                // "select [col] "
                .append(SELECT).append($SPACE).append(resultMapSeq).append($SPACE)
                // "select [col] from "
                .append(FROM).append($SPACE)
                // "select [col] from `database`.`table` "
                .append(frame.database()).append($SPOT).append(frame.table()).append($SPACE)
                // "select [col] from `database`.`table` where [`col1` = 'val1' and `col2` = 'val2'] "
                .append(WHERE).append($SPACE).append(conditionSeq.toString()).append($SPACE);

        // sort sql.
        Sort sort = null;
        if((sort = sortOptional.orElse(null)) != null){
            List<String> sort_columns = _fNamesToQueryColumns(sort.fields,frame,true,false);
            // "select [col] from `database`.`table` where [`col1` = 'val1' and `col2` = 'val2'] order by col1,col2 desc "
            sqlBuild
                    .append(ORDER_BY).append($SPACE)
                    .append(String.join($DOT,sort_columns)).append($SPACE)
                    .append(sort.symbol).append($SPACE);
        }

        // limit sql.
        if(limitOne){
            // "select [col] from `database`.`table` where [`col1` = 'val1' and `col2` = 'val2'] order by col1,col2 desc limit 1 "
            sqlBuild.append(LIMIT).append($SPACE).append(1).append($SPACE);
        }else {
            // limit sql.
            Limit limit = null;
            if((limit = limitOptional.orElse(null)) != null){
                // "select [col] from `database`.`table` where [`col1` = 'val1' and `col2` = 'val2'] order by col1,col2 desc limit offset,size "
                sqlBuild
                        .append(LIMIT).append($SPACE)
                        .append(limit.offset).append($DOT).append(limit.size).append($SPACE);
            }
        }

        final String SQL = sqlBuild.toString();
        return SQL;
    }

}
